#ifndef _OPENGLFRAME_
#define _OPENGLFRAME_

#include <QtOpenGL/QGLWidget>
#include <QtGui>

#include "../meshdef/cgaltypes.h"
#include "../enumtypes.h"
#include "drawable.h"
#include "pose.h"

#include "../arcball.h"
#include "../texture.h"
#include "../selection.h"
//~ #include "../deformationShape_VSGPU.h"
//~ #include "../deformationShape_FSGPU.h"
//~ #include "../deformationShape_FSGPU_L1.h"

//~ #include "../meshdef/motion.h"
//~ #include "../meshdef/animator.h"

#include "../meshdef/mesh.h"
#include "../meshdef/meshWithSkeleton.h"
#include "../meshdef/skeleton.h"
#include "../core/constraints.h"
#include "../meshdef/deformationMesh.h"
#include "../meshdef/deformationSkelMesh.h"

#include <vector>
#include <map>
#include <algorithm>

/// Maps objects to their keypoint-related transformations
typedef std::map<DrawableInstance*, Transformation> StanceMap;

/// Maps a key point to a stance map.
typedef std::map<DrawableInstance*, StanceMap> KeyStances; 

/// A Collection of Pointers to Drawable Instances
typedef std::vector<DrawableInstance*> InstanceCollection;

/// Interaction modes
enum InteractionMode {SELECT_MODE, RESTRICTION_MODE, KEY_MODE, ANIMATE_MODE};

/// A wrapper over QGLWidget.
/// Exhibits a collection of drawable instances.
class OpenGLFrame: public QGLWidget {
   
   Q_OBJECT
   
public:
   /// Constructor. 
   OpenGLFrame (QWidget* parent=0, const QGLWidget* shareWidget = 0, Qt::WFlags f=0);
   ~OpenGLFrame();

   /// Sets the interaction mode
   void setInteractionMode (InteractionMode m);  

   /// Adds an object to the scene. 
   /// @param obj pointer to a drawable whose instance will be added to the scene.
   /// @param deformable is true if this is the object which will be deformed based on the 
   /// the other objects' positions and orientations. Only one such object is allowed.
   void addObject (Drawable* obj, bool deformable = false);

   /// Adds a new keypoint to the scene.
   void addKeyPoint (); 

protected:
   /// Adds a new restriction to the scene.
   void addRestriction ();

public slots:
   /// Implements the "cut" operation. Copies selected objects to the cutbuffer and removes them.
   void cut ();

   /// Implements the "paste" operation. Appends objects from the cutbuffer to the objects array.
   void paste ();

   /// Implements the "copy" operation. Copies selected objects to the cutbuffer.
   void copy ();
   
   /// Zoom in operation.
   void zoomIn ();

   /// Zoom out operation.
   void zoomOut ();

   /// Zoom All operation.
   void zoomAll ();

   /// Groups the selected objects
   void makeGroup ();

   /// Ungroups the selected objects
   void breakGroup ();

protected:

   /// Sets the interaction mode
   InteractionMode                       interactionMode;  

   /// These objects will be drawn in the frame
   InstanceCollection 
      objects,       ///< generic objects whose transformations depend on the keyframe points
      keyPoints,     ///< keyframe points
      cutBuffer;     ///< cut buffer for storing "cut" objects
      
   /// Indices of currently selected objects.
   std::vector<int> selected;

   /// Position of 3D cursor
   Point cursor3D;

    /// Object stances for each keyframe point.
    KeyStances keyStances;

    /// Index of the current Key.
    unsigned int currentKey;
    
    /// A temp instance group which contains the selected objects.
    DrawableInstanceGroup group;

    /// Points to the instance which is the deformable mesh. If 0, then no deformable mesh
    /// is defined yet
    DrawableInstance* deformableMesh;
    
    /// Scene tranformation
    Transformation sceneTransform;

    /// Temp transformations used for mouse tracking
    Transformation objectToScene, sceneToObject;

    /// Temp vectors used for mouse tracking
    Vector pixelDisplacement[2];
    Point grabStart;

    /// Stores the present object transformations into keyStances
    void storeStance (int keyNumber);
 
    /// Retrieves the object transformations from keyStances
    /// @param keyNumber index for the desired pose in keyPoints
    void retrieveStance (int keyNumber);

    /// Frameset for pose animation
    FrameSet frameSet;
    
    /// A pose interpolator
    RbfPoseInterpolator *poseInterpolator;
    
    /// Builds frameSet from keyPoses
    void buildFrameSet ();
    
    /// Retrieves the object transformations from the pose interpolator
    /// @param keyNumber index for the desired pose in keyPoints
    void retrievePose (const Point& point);

    /// OpenGL initialization.
    void initializeGL ();

    /// Drawing callback.
    void paintGL();
    
    /// Resize callback.
    /// @param width the width of the new window.
    /// @param height the height of the new window.
    void resizeGL (int width, int height);

    /// Mouse callback functions
    void mousePressEvent (QMouseEvent *event);
    void mouseMoveEvent (QMouseEvent *event);
    void mouseReleaseEvent (QMouseEvent *event);
    void wheelEvent(QWheelEvent *e);
    void keyPressEvent(QKeyEvent *e);
    
   Point screenToWorldOrtho(Point p);
   Point getPointOverFloor(Point p);
   /// Returns the world displacement vector defined by both an start and end point.
   /// For this process is used an orthographic projection
   Vector getWorldDisplacement(const Point &s, const Point &e);
   
   /// An arcball object
   ArcBall                arcball;
   float                  tM[16];   // transformation matrix
   float                  shadowMatrix[16]; // Shadow matrix
   bool                   was_ckeypointselected; // 
   bool                   is_shadowdragging;     // 
   
   /// //////////////////////////////////////////////////////////////////////////////////////
public:
   
   void setGLview(); /// It sets the projection/modelview matrices
   void resetAll(); /// Resets all deformation data
   
   void setRotationCompTech(RotationCompTech r);
   void meshRenderMode(MeshRenderMode mrm)      { meshrendermode_mask = mrm; }
   void viewItemOn(ViewItems v)                 { viewitems_mask = viewitems_mask | v; } // inclusive-OR
   void viewItemOff(ViewItems v)                { viewitems_mask = viewitems_mask ^ v; } // exclusive-or
   void lightingOnOff(bool l)                   { is_lightingOn = l; }
   bool isProjectionOrtho()                     { return isProjOrtho; }
   void orthographic(bool o) { 
      (o)?(isProjOrtho=true):(isProjOrtho=false);
   }
   
   /// Capture setting functions
   void set_pic_capturing(bool f)          { is_pic_capturing = f; }
   void set_mesh_capturing(bool f)         { is_mesh_capturing = f; }
   bool get_pic_capturing()                { return is_pic_capturing; }
   bool get_mesh_capturing()               { return is_mesh_capturing; }
   
   /// Loads a new polyhedron from an off file. 
   /// @param filename name of file in off format.
   /// @return true if polyhedron loading was successful.
   bool loadShape(const QString& filename);
   
   /// Saves a new polyhedron on a file. 
   /// @param filename name of file in off format.
   /// @return true if polyhedron saving was successful.
   bool saveShapeOFF(const QString& filename);
   
   /// Loads a poses file. 
   /// @param filename name of file in off format.
   /// @return true if the operation was successful.
   bool load_poses(const QString& filename);
   
   bool load_skeleton(const QString& filename);
   
   bool loadSkinning(const QString& filename);
   
   /// Saves the poses data
   void saveAsPoses(const QString& filename);
      
   /// Builds n clones of the current model. They are possitioned in a nice way
   void buildModelClones(unsigned int n);
   
   /// Tries to execute a deformation process
   void executeDeformation();
   
private:
   void genWorldLimits();
   void loadKeyPointSpriteTexture();
   
   // Draws the objects constraints
   void drawObjectsConstraints();
   void drawCurrentKeyPoint();
   void drawKeyPoints();
   void renderInfo();
   
   // Draw functions for selection purposes
   void drawObjectsConstraints_Select();
   void drawCurrentKeyPoint_Select();
   void drawKeyPoints_Select();
   void drawShadowCurrentKeyPoint_Select();
   void drawShadowObjectsConstraints_Select();
   
   void drawFloor();
   // This function is from http://www.devmaster.net community.
   void createShadowProjectionMatrix(float *l, float *e, float *n);
   
private:
   bool                                is_lightingOn;
   bool                                isProjOrtho;     // true when projection is perspective. false when perspective
   int                                 meshrendermode_mask;
   unsigned int                        viewitems_mask;
   Texture                            *spriteKeyPoints; // key points' texture
   DrawableInstance                   *keyPointCurrent; // the current keypoint
   Selection                           selection;       // the performer of selection operations
   Texture                            *texFloor;
   int                                 id_picgrab;   // identifier of the current picture to be saved
   
   /// opengl area
   float                               fovy;         // Specifies the field of view angle, in degrees, in the y direction
   float                               zoomFactor;   // zoom factor
   float                               cameye[3];    // camera's eye
   float                               camcenter[3]; // Specifies the position of the reference point
   float                               mincoo[3];    // minimum point coordinates of the GL world
   float                               maxcoo[3];    // maximum point coordinates of the GL world
   GLdouble                            mMtx[16], pMtx[16];  /// modelview/projection matrix
   GLint                               viewport[4];         /// the viewport
   float                               WorldWidth;
   float                               WorldHeight;
   float                               objDistance;
   float                               nearplane;
   float                               farplane;
   Point                               floor[4]; // four points of the plane
   Plane                               pfloor;   // floor's plane
   
   /// time info
   float                               cfps;
   float                               lastTime;
   time_t                              start, end;
   unsigned                            frame;
   
   /// //////////////////////////////////////////////////////////////////////////////////////
   vector<Mesh*>                       models;
   vector<Vector>                      trans; // vector translations of clones;
   
   //~ Shape3D                        *currentmodel;
   DeformationMesh               *deformer;
   MeshWithSkeleton              *currentmodel;
   //~ DeformationSkelMesh           *deformer;
   //~ DeformationShape_VSGPU         *deformer;
   //~ DeformationShape_FSGPU         *deformer;
   //~ DeformationShape_FSGPU_L1      *deformer;
   Constraints                    constraints;
   Constraints                    i_constraints; // initial constraints
   unsigned int                        njoints;
   int                                 poseid;
   RotationCompTech                    rotationCompTech;
public:
   
   /// Animation data
   std::vector<Point>                  interKeyPoints; //< the interpolated keypoints 
   std::vector<std::vector<Vector> >   poses; //< a list of poses 
   
   //~ Animator      animator;
   //~ Motion        motion;
   
   void play();
   void stop();
   void pause();
   
   int                                 xini, yini;
   
   bool                                is_pic_capturing;
   bool                                is_mesh_capturing;
   
public slots:
   void record();
   
   void animate(int animindex);
   
   /// Animates the currentmodel using a list of poses
   void animate_poses(int index_pose);
};

#endif
